package ui;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.Vector;

import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;

/**
 * A custom JComboBox class that allows for a small field and a large drop-down list
 * UI size.
 * <p>
 * There are two preferred sizes: one for the field and one for the list.  In
 * general, the original preferred size is bound to the list.  However, when the UI
 * is drawn, the size of the field is used instead.  Methods that normally affect
 * the original preferred size will affect the size of the list.  For example,
 * setting the prototype display value of JComboBox will affect the size of the list,
 * but will not affect the size of the field.
 * <p>
 * Depending on the layout manager used, the actual size of CustomComboBox may vary. 
 * 
 * @author Aaron Chen
 * @version 3/18/2014
 * @param <E> The generic object type that the combo box will handle.
 */
@SuppressWarnings("serial")
public class CustomComboBox<E> extends JComboBox<E> {
  
  /** The dimension of the field's preferred size. */
  private Dimension my_field_dimension;

  /**
   * Creates a CustomComboBox with a default data model.
   */
  public CustomComboBox() {
    super();
    setParameters();
  }

  /**
   * Creates a CustomComboBox that takes its items from an existing ComboBoxModel.
   * @param the_model the ComboBoxModel that provides the displayed list of items
   */
  public CustomComboBox(final ComboBoxModel<E> the_model) {
    super(the_model);
    setParameters();
  }

  /**
   * Creates a JComboBox that contains the elements in the specified array.
   * @param the_items an array of objects to insert into the combo box
   */
  public CustomComboBox(final E[] the_items) {
    super(the_items);
    setParameters();
  }

  /**
   * Creates a JComboBox that contains the elements in the specified Vector.
   * @param the_items an array of vectors to insert into the combo box
   */
  public CustomComboBox(final Vector<E> the_items) {
    super(the_items);
    setParameters();
  }
  
  /**
   * A constructor helper method that sets up the extra variables used in CustomComboBox.
   */
  private void setParameters() {
    setUI(new CustomComboBoxUI());
    my_field_dimension = super.getPreferredSize();
  }
  
  /**
   * Sets the preferred size of the field.  If the preferred size is null, the UI will
   * be asked for the preferred size.
   * @param the_d The new preferred size of the field.
   */
  public void setFieldSize(final Dimension the_d) {
    my_field_dimension = the_d;
  }
  
  /**
   * If the field's preferred size has been set to a non-null value just returns it.  If
   * the UI delegate's getPreferredSize method returns a non null value then return that;
   * otherwise defer to the component's layout manager.
   * @return the value of the field's preferredSize property
   */
  public Dimension getFieldSize() {
    return getPreferredSize();
  }
  
  /**
   * Sets the preferred size of the drop-down list.  If preferred size is null, the
   * UI will be asked for the preferred size.
   * @param the_d The new preferred size of the drop-down list.
   */
  public void setDropDownListSize(final Dimension the_d) {
    super.setPreferredSize(the_d);
  }
  
  /**
   * If the drop-down list's preferred size has been set to a non-null value just
   * returns it.  If the UI delegate's getPreferredSize method returns a non null value
   * then return that; otherwise defer to the component's layout manager.
   * @return the value of the drop-down list's preferredSize property
   */
  public Dimension getDropDownListSize() {
    return super.getPreferredSize();
  }
  
  /**
   * {@inheritDoc}
   * <p>
   * Returns either the field's preferred size or the drop-down list's preferred size,
   * whichever is smaller.
   */
  @Override
  public Dimension getPreferredSize() {
    final Dimension d = super.getPreferredSize();
    final double min_width = Math.min(d.getWidth(), my_field_dimension.getWidth());
    final double min_height = Math.min(d.getHeight(), my_field_dimension.getHeight());
    d.setSize(min_width, min_height);
    return d;
  }
  
  
  /**
   * The look and feel interface for JComboBox.  This interface allows for larger
   * drop-down lists than the standard Swing interfaces.
   * 
   * @author Aaron Chen
   * @version 3/18/2014
   */
  private class CustomComboBoxUI extends MetalComboBoxUI {
    
    /**
     * {@inheritDoc}
     */
    @Override
    protected ComboPopup createPopup() {
      final BasicComboPopup popup = new BasicComboPopup(CustomComboBox.this) {
        /** {@inheritDoc */
        @Override
        protected Rectangle computePopupBounds(final int the_x, final int the_y,
                                               final int the_w, final int the_h) {
          final int max_width = Math.max(CustomComboBox.super.getPreferredSize().width, the_w);
          return super.computePopupBounds(the_x, the_y, max_width, the_h);
        }
      };
      popup.getAccessibleContext().setAccessibleParent(CustomComboBox.this);
      return popup;
    }
    
  }
  
}
